/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package mux

import (
	"bytes"
	"html/template"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"net/http"
)

// TemplateHandler 接口用于接收并渲染模板数据
type TemplateHandler interface {
	// url 是当前请求的路径地址
	// views 是 action 中设置的模板名称
	// data 是 action 中设置的模板数据
	// writer 为 http.ResponseWriter
	// 如果发生错误直接返回错误
	HandleViews(url string, views []string, data map[string]interface{}, writer http.ResponseWriter) error
}

type TplContainer struct {
	tpls   map[string]*template.Template
	locker *sync.RWMutex
	tplHandlers []func(tpl *template.Template)
}

func NewTplContainer(tplHandlers []func(tpl *template.Template)) *TplContainer {
	return &TplContainer{tpls: make(map[string]*template.Template, 30), locker: &sync.RWMutex{}, tplHandlers:tplHandlers}
}

func (tc *TplContainer) HandleViews(url string, views []string, data map[string]interface{}, writer http.ResponseWriter) error {
	var (
		tpl *template.Template
	)
	if Env.Server.CacheTemplate {
		tc.locker.RLock()
		tpl = tc.tpls[url]
		tc.locker.RUnlock()
	}
	if tpl == nil {
		var err error
		if Env.Server.Debug {
			err = makeViewFiles(url, views)
			if err != nil {
				return err
			}
		}
		for _, view := range views {
			file := viewFile(view)
			b, err := ioutil.ReadFile(file)
			if err != nil {
				return err
			}
			s := string(b)
			if tpl == nil {
				tpl = template.New(view)
				for _, handler := range tc.tplHandlers {
					handler(tpl)
				}
				_, err = tpl.Parse(s)
				if err != nil {
					return err
				}
			} else {
				_, err = tpl.New(view).Parse(s)
				if err != nil {
					return err
				}
			}
		}
		if Env.Server.CacheTemplate {
			tc.locker.Lock()
			tc.tpls[url] = tpl
			tc.locker.Unlock()
		}
	}
	writer.WriteHeader(http.StatusOK)
	return tpl.Execute(writer, data)
}

func viewFile(view string) string {
	return filepath.Join(Env.Dir.Views, view + Env.Server.TemplateExt)
}

func makeViewFiles(url string, views []string) error {
	for key, view := range views {
		file := viewFile(view)
		_, err := os.Stat(file)
		if os.IsNotExist(err) {
			var content []byte
			if key == 0 {
				content = newLayout(view, url, Env.Languages.Default, views[1:])
			} else {
				content = newSection(view)
			}
			dir := filepath.Dir(file)
			err = os.MkdirAll(dir, 0777)
			if err != nil {
				return err
			}
			err = ioutil.WriteFile(file, content, 0777)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func newLayout(layout, url, language string, sections []string) []byte {
	buf := bytes.NewBuffer([]byte(`<!DOCTYPE html>
<html lang="`+language+`">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
	<!--generated from url: "` + url + `"-->
	<h1 style="text-align: center">` + strings.ToUpper(layout) + `</h1>`))
	for _, section := range sections {
		buf.WriteString("\n\t{{block " + `"` + section + `"` + " .}}{{end}}")
	}
	buf.WriteString("\n</body>\n</html>")
	return buf.Bytes()
}

func newSection(section string) []byte {
	return []byte("{{define " + `"` + section + `"` + "}}\n\t" + `<h2 style="text-align: center">` + strings.ToUpper(section) + `</h2>` + "\n{{end}}")
}
